#include "common.h"

#define BUFSIZE_MAX 1024

int g_connecting = CONNECTING;  //正在连接主机
int g_searching  = SEARCHING;   //正在查找IP

sem_t s;
char *filename;

//寻找主机
void *searching_host(void *arg)
{
    pthread_detach(pthread_self());
    fprintf(stderr, "正在寻找主机");
    while(g_searching == SEARCHING)
    {
        fprintf(stderr, ".");
        usleep(100*1000);
    }
    printf("%s", g_searching == SEARCH_SUCCESS ? "[OK]\n" : "[FAIL]\n");
    sem_post(&s);

    pthread_exit(NULL);
}

//连接服务器
void *connecting_host(void *arg)
{
    pthread_detach(pthread_self());
    fprintf(stderr, "正在远端服务器");
    while(g_connecting == CONNECTING)
    {
        fprintf(stderr, ".");
        usleep(100*1000);
    }
    printf("%s", g_connecting == CONNECT_SUCCESS ? "[OK]\n" : "[FAIL]\n");
    sem_post(&s);

    pthread_exit(NULL);
}

//拆分地址信息
void arg_parser(char *link, char **host, char **filepath)
{
    assert(link);       //确保 link 不为NULL
    assert(host);
    assert(filepath);

    if(link[strlen(link)-1] == '/') //链接尾部不能是'/'
    {
        fprintf(stderr, "非法链接.\n");
        exit(0);
    }

    char *h, *f;
    h = f = link;
    
    char *delim1 = "http://";
    char *delim2 = "https://";
    if(strstr(link, delim1) != NULL)    //去除[http://] 或[https://]
    {
        h += strlen(delim1);
    }
    else if(strstr(link, delim2) != NULL)
    {
        h += strlen(delim2);
    }

    f =strstr(h, "/");  //去除[cdn.kernel.org],获得下载位置[/pub/linux/kernel/v6.x/linux-6.10.8.tar.xz]
    if(f == NULL)
    {
        fprintf(stderr, "非法链接.\n");
        exit(0);
    }
    f += 1;             //去除[/]

    *host = calloc(1, 256);
    *filepath = calloc(1, 1048); 

    memcpy(*host, h, f-h-1);            //解引用一次host变为一级指针,指向[cdn.kernel.org]
    memcpy(*filepath, f, strlen(f));    //指向[pub/linux/kernel/v6.x/linux-6.10.8.tar.xz]
}

void progress(long long nread, long long filesize)
{
	struct winsize ws;
	ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);

	int bar_len = ws.ws_col-32;
	bar_len = bar_len > 60 ? 60 : bar_len;

	int rate = filesize/bar_len;
	int cur  = nread/rate;

	char *total = calloc(1, 16);
	if(filesize < 1024)
		snprintf(total, 16, "%llu", filesize);
	else if(filesize >= 1024 && filesize < 1024*1024)
		snprintf(total, 16, "%.1fKB", (float)filesize/1024);
	else if(filesize >= 1024*1024 && filesize < 1024*1024*1024)
		snprintf(total, 16, "%.1fMB", (float)filesize/(1024*1024));


	char *bar = calloc(1, 128);
	if(nread < 1024)
		snprintf(bar, 128, "\r[%llu/%s] [", nread, total);
	else if(nread < 1024*1024)
		snprintf(bar, 128, "\r[%.1fKB/%s] [", (float)nread/1024, total);
	else if(nread < 1024*1024*1024)
		snprintf(bar, 128, "\r[%.1fMB/%s] [", (float)nread/(1024*1024), total);
	free(total);

	int i;
	for(i=0; i<cur; i++)
		snprintf(bar+strlen(bar), 128-strlen(bar)-i, "%s", "#");

	for(i=0; i<bar_len-cur-1; i++)
		snprintf(bar+strlen(bar), 128-strlen(bar)-i, "%s", "-");

	snprintf(bar+strlen(bar), 128-strlen(bar),
			"] [%.1f%%]%c", (float)nread/filesize*100,
			nread==filesize?'\n':' ');
	fprintf(stderr, "%s", bar);
	free(bar);
}

int main(int argc, char const *argv[])
{
    printf("请输入下载链接[直接回车将下载: https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.10.8.tar.xz]\n");
    char *link = calloc(1, 1024);
    fgets(link, 1024, stdin);

    if(link[0] == '\n')
        strcpy(link, "https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.10.8.tar.xz");
    else
        strtok(link, "\n");

    char *host;
    char *filepath;
    arg_parser(link, &host, &filepath); //获取域名和下载地址
    free(link);

    sem_init(&s, 0, 0);
    pthread_t t;
    pthread_create(&t, NULL, searching_host, NULL);

    //取得指定域名IP
    struct hostent *he = gethostbyname(host);   //系统函数
    if(he == NULL && errno == HOST_NOT_FOUND)
    {
        g_searching = SEARCH_FAIL;
        sem_wait(&s);   // 等待进度线程输出所有信息之后，再输出下面的信息
        printf("主机名有误或DNS配置有误\n");
        exit(0);
    }
    else if(he == NULL)
    {
        g_searching = SEARCH_FAIL;
        sem_wait(&s);
        printf("非法链接\n");
        exit(0);
    }
    g_searching = SEARCH_SUCCESS;
    sem_wait(&s);

    //从 gethostbyname() 获取的服务器IP地址集合
    struct in_addr **addr_list = 
                        (struct in_addr **)(he->h_addr_list);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in srvaddr;
    socklen_t len = sizeof(srvaddr);
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_addr = *addr_list[0];
    srvaddr.sin_port = htons(80);

    //将sockfd设置为非阻塞
    //便于可以监控到三次握手无法立即完成的状态
    long state = fcntl(sockfd, F_GETFL);
    state |= O_NONBLOCK;
    fcntl(sockfd, F_SETFL, state);

    //一个线程显示连接进度
    pthread_create(&t, NULL, connecting_host, NULL);

    sleep(1);
    if(connect(sockfd, (struct sockaddr *)&srvaddr, len) == -1)
    {
        fd_set wset;
        FD_ZERO(&wset);
        FD_SET(sockfd, &wset);

        struct timeval tv = {10, 0};    //10秒后超时
        long state = fcntl(sockfd, F_GETFL);
        state &= ~O_NONBLOCK;
        fcntl(sockfd, F_SETFL, state);

        //n代表select()成功等到的已就绪的套接字的个数
        int n = select(sockfd+1, NULL, &wset, NULL, &tv);
        if(n < 0)
        {
            perror("select()failed");
            exit(0);
        }
        if(n == 0)
        {
            g_connecting = CONNECT_FAIL;
            sem_wait(&s);
            printf("链接超时\n");
            exit(0);
        }

        //判定sockfd就绪
        if(FD_ISSET(sockfd, &wset))
        {
            if(connect(sockfd, (struct sockaddr *)&srvaddr, len) == 0)
            {
                g_connecting = CONNECT_SUCCESS;
            }
            else
            {
                perror("connect error");
                exit(0);
            }
        }
    }
    g_connecting = CONNECT_SUCCESS;

    FILE *fp;
    long long curlen = 0LL;     //存储文件大小
    if(strstr(filepath, "/"))
        filename = strrchr(filepath, '/')+1;
    else
        filename = filepath;
    
    if(access(filename, F_OK))
    {
        fp = fopen(filename, "w");  //不存在则新建文件
    }
    else
    {
        fp = fopen(filename, "a");  //存在则追加方式打开文件

        //获取已有文件大小
        struct stat fileinfo;
        stat(filename, &fileinfo);
        curlen = fileinfo.st_size;
    }

    if(fp == NULL)
    {
        perror("fopen() failed");
        exit(0);
    }

    //设置普通文件为不缓冲状态，防止突然断网程序退出导致数据丢失
    setvbuf(fp, NULL, _IONBF, 0);

    //发送HTTP请求报文
    char *sndbuf = calloc(1, 1024);
    http_request(sndbuf, 1024, filepath, host, curlen);

    int n = send(sockfd, sndbuf, strlen(sndbuf), 0);
    if(n == -1)
    {
        perror("send() failed");
        exit(0);
    }
    free(sndbuf);

    long long total_bytes = curlen;

    //收到并解析HTTP响应报文
    char *httphead = calloc(1, 2048);
    n = 0;
    while(1)
    {
        read(sockfd, httphead+n, 1);
        n++;
        if(strstr(httphead, "\r\n\r\n"))
            break;
    }
    printf("HTTP响应头部:\n%s", httphead);

    //接收文件内容，并妥善保存下来
    long long size; //要下载文件的总大小
    long long len2; //本次要下载的大小
    size = get_size(httphead);
    len2 = get_len(httphead);
    printf("文件总大小;%lld\n", size);
    printf("本次要下载的大小:%lld\n", len2);


    //检查HTTP响应报文
    //返回：真：服务器正常返回
    //      假：发生错误
    if(!check_response(httphead))
    {
        //文件已经下载
        if(curlen == size && curlen != 0)
        {
            fprintf(stderr, "该文件已下载\n");
        }

        //其他错误
        if(!access(filename, F_OK) && curlen == 0)
            remove(filename);

        exit(0);
    }
    free(httphead);

    //真正接收文件内容
    int bufsize = size > BUFSIZE_MAX ? BUFSIZE_MAX : size;
    char *buf = calloc(1, bufsize);

    while(1)
    {
        int n =read(sockfd, buf, bufsize);

        if(n == 0)
            break;

        if(n == -1)
        {
            perror("读取服务器资源失败");
            break;
        }

        fwrite(buf, 1, n, fp);

        total_bytes += n;
        progress(total_bytes, size);

        if(total_bytes >= size)
        {
            printf("[Down]\n");
            break;
        }
    }

    fclose(fp);
    free(buf);

    // pthread_exit(NULL);
    return 0;
}
